#pragma once
#include <iostream>
#include <sys/select.h>
#include <sys/time.h>
#include <string>
#include "Sock.hpp"
#include "Log.hpp"


using std::cout;
using std::endl;
// select 只完成读取，写入异常不做处理
static const int BITS = 8;
static const int NUM = sizeof(fd_set) * BITS;
static const int FD_NONE = -1;
class SelectServer
{
public:
    SelectServer(const uint16_t &port = 8081) : _port(port)
    {
        _listensock = Sock::Socket();
        Sock::Bind(_listensock, _port);
        Sock::Listen(_listensock);
        logMessage(DEBUG, "create base socket success");
        // 将文件描述符设定数组进行初始化
        for (int i = 0; i < NUM; i++)
        {
            _fd_array[i] = FD_NONE;
        }
        // 约定0号下标存放listernsock
        _fd_array[0] = _listensock;
    }
    void Start()
    {
        // fd_set rfds;    // 创建要设置的读取文件描述符位图
        // FD_ZERO(&rfds); // 将其清零
        while (true)
        {

            // struct timeval timeout={5,0};
            //  listernsock 本质也是一种IO，没有链接时会一直等待链接，有链接时提供服务。所以我们要设置listensock，当资源就绪在调用accept
            // FD_SET(_listensock, &rfds); // 将listensock添加到读文件描述符集中
            // int n = select(_listensock + 1, &rfds, nullptr, nullptr, nullptr);//阻塞等待
            // int n = select(_listensock + 1, &rfds, nullptr, nullptr, &timeout);//非阻塞等待
            DebugPrint();
            fd_set rfds;             // 创建要设置的读取文件描述符位图
            FD_ZERO(&rfds);          // 将其清零
            int maxfd = _listensock; // 当前最大文件描述符
            // 将文件描述符数组中的要被设置的文件描述符加入到rfds位图中
            for (int i = 0; i < NUM; i++)
            {
                if (_fd_array[i] == FD_NONE)
                    continue;
                FD_SET(_fd_array[i], &rfds);
                if (maxfd < _fd_array[i])
                    maxfd = _fd_array[i];
            }
            // 使用select进行等待
            int n = select(maxfd + 1, &rfds, nullptr, nullptr, nullptr); // 阻塞等待
            switch (n)
            {
            case 0:
                // 没有链接到来
                logMessage(DEBUG, "time out...");
                break;
            case -1:
                // 读取失败
                logMessage(WARNING, "select error:%f : %s", errno, strerror(errno));
                break;
            default:
                // success 链接
                logMessage(DEBUG,"get a new link ...");
                HandlerEvent(rfds);
                break;
            }
        }
    }
    ~SelectServer()
    {
        if (_listensock >= 0)
            close(_listensock);
    }

private:
    void HandlerEvent(const fd_set &rfds) // rfds是一个集合，里面有可能存在多个sock
    {
        // 先遍历fdarr找到合法的文件描述符
        for (int i = 0; i < NUM; i++)
        {
            if (_fd_array[i] == FD_NONE)
                continue;
            // 合法的不一定都是就绪的
            if (FD_ISSET(_fd_array[i], &rfds))
            {
                // 合法且就绪
                if (_fd_array[i] == _listensock)
                {
                    Accepter(); // listernsock就绪，获取新连接
                }
                else
                {
                    //普通文件描述符，此时需要读取数据进行处理
                    Recv(i);
                }
            }
        }
    }
    void Accepter()
    {
        std::string clientip;
        uint16_t clientport = 0;

        // listernsock 上面的读取事件已经就绪
        int sock = Sock::Accept(_listensock, &clientip, &clientport); // accept不会阻塞
        if (sock < 0)
        {
            logMessage(WARNING, "Accept error");
            return;
        }
        logMessage(DEBUG, "get a new link success:[%s : %d]: %d", clientip.c_str(), clientport, sock);
        //此时我们不能直接读取数据，因为我们只是获取了链接，并不清楚该链接的数据是否就绪，我们还需要将sock还给select
        //让其帮我们去等待

        //将提供服务的sock添加到文件描述符表中,让select等待
        int pos = 1;
        for(;pos<NUM;pos++)
        {
            if(_fd_array[pos] == FD_NONE)
                break;
        }
        if(pos == NUM)//链接满了
        {
            logMessage(WARNING,"select server alredy full,close:%d",sock);
            close(sock);
        }
        else{
            _fd_array[pos] = sock;
        }
    }
    void Recv(int pos)
    {
        logMessage(DEBUG,"message in,get IO event:%d",_fd_array[pos]);
        char buffer[1024];
        int n = recv(_fd_array[pos],buffer,sizeof(buffer)-1,0);//无法保证读取的是一整个报文
        if(n>0)
        { 
            buffer[n]=0;
            logMessage(DEBUG,"Client[%d]# %s",_fd_array[pos],buffer);
        }
        else if(n==0)
        {
            //客户端退出
            logMessage(DEBUG,"Client[%d] quite,mee too",_fd_array[pos]);
            //关闭sock
            close(_fd_array[pos]);
            _fd_array[pos] = FD_NONE;
        }
        else{
            //读取错误
            logMessage(WARNING, "%d sock recv error, %d : %s", _fd_array[pos], errno, strerror(errno));
            close(_fd_array[pos]);
            _fd_array[pos] = FD_NONE;
        }
    }
    void DebugPrint()
    {
        cout << "_fd_array[]: ";
        for(int i = 0; i < NUM; i++)
        {
            if(_fd_array[i] == FD_NONE) continue;
            cout << _fd_array[i] << " ";
        }
        cout << endl;
    }

private:
    uint16_t _port;
    int _listensock;
    int _fd_array[NUM];
};